﻿using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Red.Controls.Tile
{
    internal class Move
    {
        #region Public Properties

        public TileImpl Tile { get; set; }

        public int Row { get; set; }
        public int Column { get; set; }

        public int RowSpan { get; set; } = 1;
        public int ColumnSpan { get; set; } = 1;

        #endregion Public Properties

        public override string ToString() { return $"{Tile} ({Row},{Column})"; }

        public override int GetHashCode() { return Tile.GetHashCode(); }

        public override bool Equals( object obj ) { return Tile.Equals( ( obj as Move )?.Tile ); }
    }

    internal class LayoutGuide
    {
        #region Private Types

        private class Cell
        {
            public int Row { get; set; }
            public int Column { get; set; }
        }

        #endregion Private Types

        #region Private Data

        private List<List<TileImpl>> _cell = new List<List<TileImpl>>();
        private Dictionary<TileImpl, List<Cell>> _tileCellMap = new Dictionary<TileImpl, List<Cell>>();

        #endregion Private Data

        #region Public Properties

        public int Rows { get; private set; }
        public int Columns { get; private set; }

        #endregion Public Properties

        #region Operator Overloads

        public List<TileImpl> this[ int i ]
        {
            get { return _cell[ i ]; }
            set { _cell[ i ] = value; }
        }

        #endregion Operator Overloads

        #region Private Methods

        private int CountTrailingEmptyRows()
        {
            int emptyRows = 0;

            for ( int row = Rows - 1; row > 1; --row )
            {
                for ( int column = 0; column < Columns; ++column )
                {
                    if ( _cell[ row ][ column ] != null )
                    {
                        return emptyRows;
                    }
                }

                ++emptyRows;
            }

            return emptyRows;
        }

        #endregion Private Methods

        #region Public Methods

        public void AddRow( int count = 1 )
        {
            for ( int i = 0; i < count; ++i )
            {
                List<TileImpl> column = new List<TileImpl>( Columns );

                for ( int j = 0; j < Columns; ++j )
                {
                    column.Add( null );
                }

                _cell.Add( column );
            }

            Rows += count;
        }

        public void AddColumn( int count = 1 )
        {
            foreach ( List<TileImpl> row in _cell )
            {
                row.Capacity += count;

                for ( int i = 0; i < count; ++i )
                {
                    row.Add( null );
                }
            }

            Columns += count;
        }

        public bool IsInRange( int row, int column, int rowspan, int columnspan, int potentialNewRows = 0 )
        {
            return row >= 0 && column >= 0 && row + rowspan <= ( Rows + potentialNewRows ) && column + columnspan <= Columns;
        }

        public IEnumerable<TileImpl> FindTiles( int row, int column, int rowspan, int columnspan )
        {
            HashSet< TileImpl > tiles = new HashSet<TileImpl>();

            for ( int c = 0; c < columnspan; ++c )
            {
                int subcolumn = c + column;

                for ( int r = 0; r < rowspan; ++r )
                {
                    int subrow = r + row;

                    TileImpl tile = _cell[ subrow ][ subcolumn ];

                    if ( tile != null )
                    {
                        tiles.Add( tile );
                    }
                }
            }

            return tiles;
        }

        public bool RemoveTile( TileImpl tile )
        {
            List<Cell> cells;
            if ( _tileCellMap.TryGetValue( tile, out cells ) )
            {
                foreach ( Cell cell in cells )
                {
                    _cell[ cell.Row ][ cell.Column ] = null;
                }

                _tileCellMap.Remove( tile );
                return true;
            }

            return false;
        }

        public void RemoveTiles( IEnumerable<TileImpl> tiles )
        {
            foreach ( TileImpl tile in tiles )
            {
                RemoveTile( tile );
            }
        }

        public bool IsAreaFree( int row, int column, int rowspan, int columnspan )
        {
            if ( !IsInRange( row, column, rowspan, columnspan ) )
                return false;

            for ( int y = row; y < row + rowspan; ++y )
            {
                for ( int x = column; x < column + columnspan; ++x )
                {
                    if ( _cell[ y ][ x ] != null )
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        public bool AddTile( Move move )
        {
            if ( IsAreaFree( move.Row, move.Column, move.RowSpan, move.ColumnSpan ) )
            {
                _tileCellMap[ move.Tile ] = new List<Cell>();

                for ( int row = move.Row; row < move.Row + move.RowSpan; ++row )
                {
                    for ( int column = move.Column; column < move.Column + move.ColumnSpan; ++column )
                    {
                        _cell[ row ][ column ] = move.Tile;
                        _tileCellMap[ move.Tile ].Add( new Cell() { Row = row, Column = column } );
                    }
                }

                return true;
            }

            return false;
        }

        public bool AddTiles( IEnumerable<Move> moves )
        {
            foreach ( Move move in moves )
            {
                if ( !AddTile( move ) )
                {
                    RemoveTiles( from item in moves select item.Tile );

                    return false;
                }
            }

            return true;
        }

        public Move AddTileToFirstFreeSpaceInArea( TileImpl tile, int tileRowSpan, int tileColumnSpan, int areaRow, int areaColumn, int areaRowSpan, int areaColumnSpan )
        {
            for ( int row = areaRow; row < areaRow + areaRowSpan; ++row )
            {
                for ( int column = areaColumn; column < areaColumn + areaColumnSpan; ++column )
                {
                    Move move = new Move()
                    {
                        Tile = tile,
                        Row = row,
                        Column = column,
                        RowSpan = tileRowSpan,
                        ColumnSpan = tileColumnSpan
                    };

                    if ( AddTile( move ) )
                    {
                        return move;
                    }
                }
            }

            return null;
        }

        public int TrimRows()
        {
            int rowsToRemove = CountTrailingEmptyRows();

            _cell.RemoveRange( Rows - rowsToRemove, rowsToRemove );
            Rows -= rowsToRemove;

            return rowsToRemove;
        }

        public void Clear()
        {
            _cell = new List<List<TileImpl>>();
            _tileCellMap = new Dictionary<TileImpl, List<Cell>>();
            Rows = 0;
            Columns = 0;
        }

        public LayoutGuide DeepCopy()
        {
            LayoutGuide other = new LayoutGuide()
            {
                Columns = Columns,
                Rows = Rows
            };

            other._cell = new List<List<TileImpl>>() { Capacity = _cell.Capacity };

            foreach ( var column in _cell )
            {
                other._cell.Add( new List<TileImpl>( column ) );
            }

            other._tileCellMap = new Dictionary<TileImpl, List<Cell>>();

            foreach ( var mappedCell in _tileCellMap )
            {
                other._tileCellMap[ mappedCell.Key ] = new List<Cell>( mappedCell.Value );
            }

            return other;
        }

        [Conditional( "DEBUG" )]
        public void Print()
        {
            string output = "";
            for ( int row = 0; row < Rows; ++row )
            {
                for ( int column = 0; column < Columns; ++column )
                {
                    output += ( _cell[ row ][ column ] == null ) ? "0" : "X";
                }

                output += "\n";
            }

            Debug.Print( output );
        }

        #endregion Public Methods
    }
}
